package at.jaki.ubb.crawling.appThreads;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;

import org.apache.log4j.Logger;

import at.jaki.ubb.crawling.api.filters.FilterResult;
import at.jaki.ubb.crawling.api.filters.TargetFilter;
import at.jaki.ubb.crawling.components.CEFilterWrapper;
import at.jaki.ubb.crawling.components.FilterChain;
import at.jaki.ubb.crawling.dataFinder.DataFinderManager;
import at.jaki.ubb.crawling.model.Target;
import at.jaki.ubb.crawling.model.config.FilterElementConfig;
import at.jaki.ubb.crawling.model.config.TargetQueueConfig;

/**
 * This singleton will run on a separate thread. It will watch the target queue
 * for changes, and offer the elements from the queue to the
 * {@link DataFinderManager}.
 * 
 * @author Attila Jakabfi
 * 
 */
public final class TargetQueue extends AbstractCrawlerThread {

	private static final Logger LOG = Logger.getLogger(TargetQueue.class);

	private static TargetQueue instance;

	public static synchronized TargetQueue getInstance() {
		if (instance == null) {
			instance = new TargetQueue();
		}
		return instance;
	}

	/**
	 * Priority queue that hold all the targets.
	 */
	private PriorityQueue<Target<?>> queue = new PriorityQueue<Target<?>>();

	private FilterChain<Target<?>> filterChain;

	/**
	 * private constructor for singleton.
	 */
	private TargetQueue() {
		LOG.info("Creating Target Queue and Target Filter");
		filterChain = new FilterChain<Target<?>>();
	}

	/**
	 * Will set the target filters.
	 * 
	 * @param targetQueueConfig config elem.
	 */
	public void initialize(TargetQueueConfig targetQueueConfig) {
		if (targetQueueConfig == null) {
			return;
		}
		for (FilterElementConfig targetConfig : targetQueueConfig.getTargetConfigList()) {
			String clazz = targetConfig.getClazzName();
			String logName = targetConfig.getLogName();
			try {
				Object instance = Class.forName(clazz).newInstance();
				TargetFilter filter = (TargetFilter) instance;
				CEFilterWrapper<Target<?>> wrapper = new CEFilterWrapper<Target<?>>(filter);
				wrapper.setLogName(logName);
				filterChain.addFilter(wrapper);
				LOG.info("Target filter '".concat(logName).concat("' initialized"));
			} catch (InstantiationException e) {
				LOG.error("Could not instantiate class ".concat(clazz).concat(". No default constuctor, or the class is abstract."));
				if (LOG.isDebugEnabled()) {
					e.printStackTrace();
				}
			} catch (IllegalAccessException e) {
				LOG.error(e.getClass().getName().concat(" : ") + e.getLocalizedMessage());
				if (LOG.isDebugEnabled()) {
					e.printStackTrace();
				}
			} catch (ClassNotFoundException e) {
				LOG.error(e.getClass().getName().concat(" : ") + e.getLocalizedMessage());
				if (LOG.isDebugEnabled()) {
					e.printStackTrace();
				}
			} catch (RuntimeException e) {
				LOG.error(e.getClass().getName().concat(" : ") + e.getLocalizedMessage());
				if (LOG.isDebugEnabled()) {
					e.printStackTrace();
				}
			}
		}
	}

	private PriorityQueue<Target<?>> getQueue() {
		return queue;
	}

	/**
	 * offers the first Target to the {@link DataFinderManager}. The
	 * "first target" depends on priority and filter settings.
	 */
	@Override
	protected void doWork() {

		Target<?> target = null;
		List<Target<?>> skippedTargets = new LinkedList<Target<?>>();

		while (target == null) {

			Target<?> firstTarget = pop();

			if (firstTarget == null) {
				break;
			}

			FilterResult filterResult = filterChain.doFilter(firstTarget);

			switch (filterResult) {
			case DROP:
				LOG.debug("dropping " + firstTarget);
				continue;
			case RETURN:
				skippedTargets.add(firstTarget);
				LOG.debug("skipping " + firstTarget);
				continue;
			case CONTINIUE:
				target = firstTarget;
				break;
			}
		}

		pushAll(skippedTargets);

		if (target != null) {
			DataFinderManager.getInstance().call(target);
		}
	}

	/**
	 * Returns and removes the element with the highest priority from the queue.
	 * 
	 * @return {@link Target}, can be null.
	 */
	private Target<?> pop() {
		Target<?> t = null;

		synchronized (TargetQueue.class) {
			t = getQueue().poll();
		}

		if (t != null) {
			LOG.debug("Pop: " + t);
		}
		return t;
	}

	/**
	 * Will clear all elements from the target queue
	 */
	@Override
	protected void clear() {
		synchronized (TargetQueue.class) {
			getQueue().clear();
		}
	}

	/**
	 * Inserts a target into the queue
	 * 
	 * @param t {@link Target}
	 */
	public void push(Target<?> t) {
		LOG.debug("Push: " + t);
		synchronized (TargetQueue.class) {
			getQueue().offer(t);
		}
	}

	private void pushAll(Collection<Target<?>> coll) {
		if (coll != null && coll.size() > 0) {
			LOG.debug("pusing all skipped targets back to queue");
			synchronized (TargetQueue.class) {
				for (Target<?> t : coll) {
					getQueue().offer(t);
				}
			}
		}
	}

	/**
	 * @return the number of elements waiting in the target queue
	 */
	public int getSize() {
		return queue.size();
	}

}
